﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Mbs.Collections;
using Mbs.Log;
using System.IO;
using System.Text;
using Mbs.ComponentModel.Container;

namespace Mbs.Modularity.AddIns
{ 
    [Serializable]
    [Component(Service=typeof(IAddInTree))]
    public class AddInTree:MarshalByRefObject, IAddInTree
    {
        private List<IAddIn> addIns = new List<IAddIn>();
        private IAddInNode rootNode;
        private IAddInItemCollection Map = new AddInItemCollection();

       
        private object syncObj = new object();

        public AddInTree() { }
        private static readonly ILog log = LoggerManager.GetLogger(typeof(AddInTree));

        [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
        public IEnumerable<IAddIn> AddIns
        {
            get { return addIns.ToArray(); }
        }

        [DebuggerHidden]
        public IAddInNode RootNode
        {
            get { return rootNode; }
        }

        public void Install(Stream addInStream)
        {
            Install(AddIn.Load(addInStream));
        }

        public void Install(string addInFile)
        {
            Install(AddIn.Load(addInFile));
        }

        public void Install(IAddIn addIn)
        {
            log.Info("begin install addin :" + addIn.Name);

            lock (syncObj)
            {
                if (addIn.Enabled)
                {
                    var clone = addIn.Clone();
                    foreach (var path in clone.Paths)
                    {
                        //注意：AddInTree中只有AddInItems，没有AddInPath

                        if (rootNode == null)
                            AddRootNode(path);
                        foreach (var item in path.Items)
                            AddSubItem(item);
                    }
                }
                addIns.Add(addIn);
            }
            log.Info("end install addin :" + addIn.Name);
        }

        private void AddRootNode(IAddInItemPath path)
        {
            rootNode = new AddInItem(path.AddInName, null, path.Properties);
            Map.Add(rootNode);
        }

        public IEnumerable<IAddInNode> FindAllByAddInName(string addInName)
        {
            return from item in Map
                   where item.AddInName == addInName
                   select item;
        }

        public void Uninstall(IAddIn addIn)
        {
            log.Info("begin uninstall addin :" + addIn.Name);

            if (addIn.Enabled)
                throw new Exception("not uninstall addin :" + addIn.Name + " because the addin in use.");

            lock (syncObj)
            {
                addIns.Remove(addIn);

                var rs = FindAllByAddInName(addIn.Name).ToArray();
                foreach(var item in rs)
                    Remove(item);
            }

            log.Info("begin uninstall addin :" + addIn.Name);
        }

        private void Remove(IAddInNode element)
        {
            var self = Get(element.Path);
            var parent = Get(element.ParentPath);
            if (parent != null)
            {
                parent.Items.Remove(element.Path);
                Map.Remove(element.Path);

                var strCmd = element.Properties.Get<string>("Command", null);
                if (!string.IsNullOrEmpty(strCmd))
                    strCmd.RemoveCommand();

                //ServiceLocator.UnRegister(element.Path);
            }

        }


        public IAddIn GetAddIn(string name)
        {
            return addIns.Find((item) => item.Name == name);
        }


        public bool Exists(string path)
        {
            if (path == null || path.Length == 0)
            {
                return true;
            }

            path = path.Trim('/');
            return Map.Contains(path);
        }

        public IAddInNode Get(string path)
        {
            if (path == null
                || path.Length == 0)
                return rootNode;

            path = path.Trim('/');
            return Map[path];
        }

        private void PopulateParentNode(string addInName, string path)
        {
            var items = path.Trim().TrimEnd('/').LastLeftOf('/').Split('/');

            var length = items.Length;
            for(int i = 0; i < length ; i ++)
                if (i < length - 1)
                    PopulateParentNode(addInName, items[i], items[i + 1]);
                else
                    PopulateParentNode(addInName, items[i - 1], items[i]);
        }

        private void PopulateParentNode(string addInName, string parentPath, string path)
        {
            var parent = Get(parentPath);
            if (parent == null) return;
            if (Get(path) != null) return;
 
            var props = new PropertySet(StringComparer.OrdinalIgnoreCase);
            props.Set("Id", path);

            var child = new AddInItem(addInName, parentPath, props);
            parent.Items.Add(child);
            Map.Add(child);
        }

        private void AddSubItem(IAddInNode item)
        {
            var parentNode = Get(item.ParentPath);
            if (parentNode == null)
                PopulateParentNode(item.AddInName, item.Path);

            parentNode = Get(item.ParentPath);
            if (parentNode == null) return;

            if (!parentNode.Items.Contains(item.Path))
            {
                parentNode.Items.Add(item);
                Map.Add(item);
            }
            else
            {
                IAddInNode current = Get(item.Path);
                foreach (var o in item.Items)
                    if (!current.Items.Contains(o.Path))
                    {
                        current.Items.Add(o);
                        Map.Add(o);
                    }
            }

            AddChildElements(item);
        }

        private void AddChildElements(IAddInNode element)
        {
            foreach (var item in element.Items)
                if (!Map.Contains(item.Path))
                {
                    Map.Add(item);
                    AddChildElements(item);
                }
        }

      
    }
}
